/*
 * Copyright (c) MARSIOT. All rights reserved. http://www.marsiot.com
 */

package com.marsiot.sdk;

import java.io.IOException;
import java.io.BufferedInputStream;
import java.io.InputStream;
import java.util.HashMap;
import java.util.ArrayList;
import java.util.List;
import java.util.Iterator;

import com.pi4j.io.gpio.*;
import com.pi4j.io.gpio.event.GpioPinDigitalStateChangeEvent;
import com.pi4j.io.gpio.event.GpioPinListenerDigital;
import com.pi4j.platform.Platform;
import com.pi4j.platform.PlatformAlreadyAssignedException;
import com.pi4j.platform.PlatformManager;
import com.pi4j.util.CommandArgumentParser;
import com.pi4j.util.Console;
import com.pi4j.util.ConsoleColor;
import com.pi4j.wiringpi.Gpio;
import com.pi4j.wiringpi.GpioUtil;
import com.pi4j.system.SystemInfo;
import com.pi4j.system.NetworkInfo;

import org.json.JSONObject;
import org.json.JSONArray;

import com.marsiot.agent.BaseCommandProcessor;
import com.marsiot.agent.MarsiotAgentException;

public class MyCommandProcessor extends BaseCommandProcessor {

    private HashMap<String, GpioPinDigitalMultipurpose> myGpioPinMap;

    public void onRegistrationConfirmed() {
        try {
            sendMessage(getHardwareId(), "message", "register ok"); 
        } catch (MarsiotAgentException e) {
        }

        initGpio();
    }

    private void initGpio() {
        System.out.print("GPIO init ...\n");

	//you should put the GPIO here which you whant to control 
        String[][] alias_gpio_direction = {
            { "gpio.0",  "output" },
            { "gpio.1",  "output" },
            { "gpio.2",  "output" },
            { "gpio.3",  "output" },
            { "gpio.7",  "input" },
        };

        HashMap<String, String> gpioDirectionMap = new HashMap<String, String>();
        for (int i=0; i<alias_gpio_direction.length; i++) {
            gpioDirectionMap.put(alias_gpio_direction[i][0], alias_gpio_direction[i][1]);
        }

        GpioController gpioControl = GpioFactory.getInstance();
        myGpioPinMap = new HashMap<String, GpioPinDigitalMultipurpose>();

        SystemInfo.BoardType board = SystemInfo.BoardType.RaspberryPi_3B_Plus;
        try {
            board = SystemInfo.getBoardType();
        } catch (Exception ex) {
            System.out.print("board type get exception");
        }

        GpioPinListenerDigital gpioPinListener = new GpioPinListenerDigital() {
            @Override
            public void handleGpioPinDigitalStateChangeEvent(GpioPinDigitalStateChangeEvent event) {
                String gpioStatus = ConsoleColor.conditional(event.getState().isHigh(),ConsoleColor.GREEN,ConsoleColor.RED,event.getState()).toString();
                System.out.print(event.getPin().getPin().getName() + " = " + gpioStatus + " "+event.getPin().getMode()+"\n" );
            }
        };

        for (Pin pin : RaspiPin.allPins(board)) {
            String myGpioName = "gpio."+pin.getAddress();

            if (!gpioDirectionMap.keySet().contains(myGpioName)) {
                continue;
            }

            Boolean output = false;
            if (gpioDirectionMap.get(myGpioName).equalsIgnoreCase("output")) {
                output = true;
            } else {
                output = false;
            }

            GpioPinDigitalMultipurpose provisionedPin;
            if (output) {
                provisionedPin = gpioControl.provisionDigitalMultipurposePin(pin, PinMode.DIGITAL_OUTPUT, PinPullResistance.PULL_UP);
            } else {
                provisionedPin = gpioControl.provisionDigitalMultipurposePin(pin, PinMode.DIGITAL_INPUT, PinPullResistance.PULL_UP);
            }

            myGpioPinMap.put(myGpioName,provisionedPin);
            provisionedPin.setShutdownOptions(true);
            provisionedPin.addListener(gpioPinListener);

            if (output) {
                provisionedPin.setMode(PinMode.DIGITAL_OUTPUT);
                provisionedPin.low();
            } else {
                provisionedPin.setMode(PinMode.DIGITAL_INPUT);
            }

            System.out.print(myGpioName + " init as " + gpioDirectionMap.get(myGpioName) + "\n");
        }

        System.out.print("GPIO init ok\n\n");
    }

    private String loadStreamToString(InputStream in) throws IOException {
        int ptr = 0;
        in = new BufferedInputStream(in);
        StringBuffer buffer = new StringBuffer();
        while( (ptr = in.read()) != -1 ) {
            buffer.append((char)ptr);
        }
        return buffer.toString();
    }

    public void helloWorld(String greeting, Boolean loud)
            throws MarsiotAgentException {
        String response = greeting + " World!";
        if (loud) {
            response = response.toUpperCase();
        }

        try {
            sendMessage(getHardwareId(), "response", response); 
        } catch (MarsiotAgentException e) {
        }

        System.out.print("hello world! greeting:" + greeting + ", load:" + loud + "\n");
    }

    public void getGpioStatus() throws MarsiotAgentException {
        JSONObject statusObj = new JSONObject();
        JSONArray gpioArray = new JSONArray();

        Iterator iter = myGpioPinMap.entrySet().iterator();
        while (iter.hasNext()) {
            HashMap.Entry entry  = (HashMap.Entry)iter.next();
            String key = (String)entry.getKey();
            GpioPinDigitalMultipurpose pin = (GpioPinDigitalMultipurpose)entry.getValue();

            JSONObject gpioObj = new JSONObject();
            if (pin.isHigh())
                gpioObj.put(key, "high");
            else
                gpioObj.put(key, "low");
            gpioArray.put(gpioObj);
        }
        statusObj.put("status", gpioArray);

        try {
            sendMessage(getHardwareId(), "response", statusObj.toString()); 
        } catch (MarsiotAgentException e) {
        }

        System.out.print(statusObj.toString()+"\n");
    }

    public void setGpio(String gpio, Boolean high) throws MarsiotAgentException {
        String gpioName = new String("gpio."+gpio);
        if (!myGpioPinMap.keySet().contains(gpioName)) {
            System.out.print("gpio name invalidate!\n");
            return;
        }

        GpioPinDigitalMultipurpose pin = myGpioPinMap.get(gpioName);
        if (pin.getMode() == PinMode.DIGITAL_INPUT) {
            System.out.print("gpio direction is not output!\n");
            return;
        }

        if (!high) {
            pin.low();
        } else {
            pin.high();
        }

        System.out.print("set " + gpio + " " + high + "\n");

        getGpioStatus();
    }

    public void getValueFromPython() throws MarsiotAgentException {
        String cmdBody = "python ./examples/get_value_from_python.py";
        String cmdRet = "";
        try {
            Process ps = Runtime.getRuntime().exec(cmdBody);
            cmdRet = loadStreamToString(ps.getInputStream()).trim();
            //System.out.print("cmd ret is "+ cmdRet +"\n");
        } catch(IOException ioe) {
            ioe.printStackTrace();
        }

        JSONObject jsonObject = new JSONObject(cmdRet);
        try {
            sendMessage(getHardwareId(), "response", jsonObject.toString()); 
        } catch (MarsiotAgentException e) {
        }

        System.out.print(jsonObject.toString()+"\n");
    }

    public void getInfoFromSystem() throws MarsiotAgentException {
        JSONObject jsonObj = new JSONObject();

        String cmdBody = "cat /proc/device-tree/model";
        String cmdRet = "";

        try {
            Process ps = Runtime.getRuntime().exec(cmdBody);
            cmdRet = loadStreamToString(ps.getInputStream()).trim();
        } catch(IOException ie) {
        }
        jsonObj.put("model",cmdRet);

        try {
            sendMessage(getHardwareId(), "response", jsonObj.toString()); 
        } catch (MarsiotAgentException e) {
        }

        System.out.print(jsonObj.toString() + "\n");
    }

    public void test(Integer aaa, String bbb, Boolean ccc) {
        System.out.print("aaa="+aaa+", bbb="+bbb+", ccc="+ccc+"\n");
    }

}


